﻿module Execution



open System
open System.IO
open System.Reflection
open ParsingEvents
open ParsingServices
open Parsing
open Features
open Scenarios
open Steps
open ExecutionEvents



type IBehaviors =
    abstract member Initialize : ParsingContext -> unit
    abstract member DomainName : unit -> string



type ExecutionContext internal (domain : AppDomain) = 
   member this.Domain = domain 

   interface IDisposable with
      member this.Dispose() = AppDomain.Unload(this.Domain)



type public ExecutionTrigger() =   
   //inherit MarshalByRefObject()

   let _onExecutionStarted = new Event<_>()
   let _onExecutionEnded = new Event<_>()
   let _onParsingError = new Event<ParsingError>()

   let _onFeatureStarted = new Event<SectionArgs<FeatureHeader>>()
   let _onFeatureEnded = new Event<SectionArgs<FeatureHeader>>()

   let _onScenarioStarted = new Event<SectionArgs<ScenarioHeader>>()
   let _onScenarioEnded = new Event<SectionArgs<ScenarioHeader>>()

   let _onStepStarted = new Event<SectionArgs<Step>>()
   let _onStepEnded = new Event<SectionArgs<Step>>()
   
   [<CLIEvent>]
   member this.OnExecutionStarted = _onExecutionStarted.Publish

   [<CLIEvent>]
   member this.OnExecutionEnded = _onExecutionEnded.Publish

   [<CLIEvent>]
   member this.OnParsingError = _onParsingError.Publish

   [<CLIEvent>]
   member this.OnFeatureStarted = _onFeatureStarted.Publish

   [<CLIEvent>]
   member this.OnFeatureEnded = _onFeatureEnded.Publish

   [<CLIEvent>]
   member this.OnScenarioStarted = _onScenarioStarted.Publish

   [<CLIEvent>]
   member this.OnScenarioEnded = _onScenarioEnded.Publish

   [<CLIEvent>]
   member this.OnStepStarted = _onStepStarted.Publish

   [<CLIEvent>]
   member this.OnStepEnded = _onStepEnded.Publish



// TODO tests
let CreateContext(language : FileInfo, dependencies : FileInfo[]) =
   if (not language.Directory.Exists) then
      failwith "Langauge file not found: '%s'" language.FullName

   let setup = AppDomainSetup()

   setup.ApplicationBase <- language.Directory.FullName

   let domain = AppDomain.CreateDomain("DSL-" + DateTime.Now.ToString(), null, setup)   
   let assembly = language.Name.Substring(0, language.Name.Length - 4)
   
   domain.SetData("TestAssembly", assembly)
   domain.SetData("TestEntry", assembly + ".BehaviorService")
   //domain.FirstChanceException.Add(fun e -> Console.WriteLine(e.Exception))
         
   let context = new ExecutionContext(domain)

   context



// TODO tests / errors
let ExtractEntry(domain : AppDomain) =    
   let assembly = domain.GetData("TestAssembly") :?> string;
   let type_ = (domain.GetData("TestEntry") :?> string) 

   domain.CreateInstanceAndUnwrap(assembly, type_) :?> ParsingService



// TODO tests
let internal CreateAggregate(language : IParsingTrigger, output : IExecutionTrigger) =
   let trigger1 = language
   let trigger2 = output
   let aggregate = ParsingContext()

   aggregate.OnExecutionEnded.Add(fun () -> 
      trigger1.ExecutionEnded()
      trigger2.ExecutionEnded()
      )

   aggregate.OnExecutionStarted.Add(fun () -> 
      trigger1.ExecutionStarted()
      trigger2.ExecutionStarted()
      )

   aggregate.OnParsingError.Add(fun (e) ->     
      trigger1.ParsingError(e.Error, e.Location)
      trigger2.ParsingError(e.Error, e.Location)      
      )

   aggregate.OnFeatureEnded.Add(fun (a) ->           
      let r1 = trigger1.FeatureEnded(a.Section) 
      let r2 = trigger2.FeatureEnded(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate.OnFeatureStarted.Add(fun (a) ->     
      //a.Continue <- trigger1.FeatureStarted(a.Section).Continue && trigger2.FeatureStarted(a).Continue      

      let r1 = trigger1.FeatureStarted(a.Section) 
      let r2 = trigger2.FeatureStarted(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate.OnScenarioEnded.Add(fun (a) ->     
      //a.Continue <- trigger1.ScenarioEnded(a.Section).Continue && trigger2.ScenarioEnded(a).Continue      
      let r1 = trigger1.ScenarioEnded(a.Section) 
      let r2 = trigger2.ScenarioEnded(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate.OnScenarioStarted.Add(fun (a) ->     
      //a.Continue <- trigger1.ScenarioStarted(a.Section).Continue && trigger2.ScenarioStarted(a).Continue      
      let r1 = trigger1.ScenarioStarted(a.Section) 
      let r2 = trigger2.ScenarioStarted(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate.OnStepEnded.Add(fun (a) ->     
      //a.Continue <- trigger1.StepEnded(a.Section).Continue && trigger2.StepEnded(a).Continue      
      let r1 = trigger1.StepEnded(a.Section) 
      let r2 = trigger2.StepEnded(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate.OnStepStarted.Add(fun (a) ->     
      //a.Continue <- trigger1.StepStarted(a.Section).Continue && trigger2.StepStarted(a).Continue      
      let r1 = trigger1.StepStarted(a.Section) 
      let r2 = trigger2.StepStarted(r1)
      
      a.Continue <- r1.Continue && r2.Continue
      a.Succeded <- r1.Succeded && r2.Succeded
      )

   aggregate



let ExecuteStream(behaviors : Stream, context : ExecutionContext, output : IExecutionTrigger) =               
   let entry = ExtractEntry(context.Domain)   
   let client = ParsingClient(context.Domain.FriendlyName)   
   let aggregate = CreateAggregate(client, output)

   entry.Start()

   try
      entry.Initialize()

      ParseStream(behaviors, aggregate)
   finally
      entry.Stop()
       


let ExecuteFile(behaviors : FileInfo, context : ExecutionContext, output : IExecutionTrigger) =
    use stream = behaviors.OpenRead()

    ExecuteStream(stream, context, output)

